iT邦幫忙

2023 iThome 鐵人賽

DAY 16
0
DevOps

一窺SRE初心者的生活:讓警報為您的人生畫下如交響樂一般的新篇章系列 第 16

日常維運5:自動化工具的維護,兼職 DevOps 的挑戰

  • 分享至 

  • xImage
  •  

前言

在日常維運系列中可以看到非常多的自動化小工具,無論是在〈維護模式〉提到為了快速進出維護模式和調整白名單而開發的小工具,還是上一篇單純為了省時間而開發的小工具,亦或是最一開始在〈前言 & 基本監控系統〉文章中曾分享過告警觸發流程中會應用到的 Lambda ,都是 SRE 日常中不可或缺的工具。

而針對這些工具的維護與更新,就也會是 SRE 的工作之一。在這裡就逐一向各位讀者分享其中的過程與遇到的挑戰。

Runtime 的升級

針對這些自動化小工具,如果它們是用 Lambda 做為執行環境的話,最常見的維護就會是程式版本上的更新。事實上,這也是筆者在進公司時最一開始所接到的任務,也是在當時因為還看不懂 Python 才誤觸 P0 警報。

AWS 會在 Lambda 的特定程式版本停止支援之前送出通知,如同在〈前言 & 基本監控系統〉提過的,SRE 將訊息轉發給產品經理後,會再由產品經理進行資訊的佈達與資源的協助。在確認 Lambda 的負責團隊後,就交給該團隊各自負責,而大量使用 Lambda 來執行自動化工具的 SRE,通常就會接到最多的 Lambda 更新工作。

在一般狀況之下,我們大概只要稍微確認程式面沒有太大的問題,直接在環境上修改程式的版本,再稍微確認一下正常執行的狀況就可以宣告完成了。不過,當程式版本過於老舊的時候,就有可能會需要重頭檢視並翻新整個程式,比如過去就曾經有從 Python 2.7 更新到 Python 3.9 的經歷,光是將東西印出來的指令都有些微的差異,做起來就會比較頭痛一些。

雖然這裡是單純寫程式而已,但筆者對 Python 的理解與基礎建立也是從這裡開始的,從一開始完全看不懂,到現在也漸漸能稍微重構或改善前人所留下來的程式了呢。

部署工具的置換

部署工具相對 runtime 升級而言,意外地複雜許多。但這裡所遇到的問題大部分不是工作上必要,反而是筆者單純想要最佳化,也同時想要提升自我能力的部分。

在這裡主要面對到的問題,是公司的主要部署工具,正在從過去主力的 CloudFormation 逐漸轉移到 Terraform。然而過去留下來的自動化工具,有可能沒有部署流水線(我們使用 GitLab CI)或仍然使用 CloudFormation ,上一篇文章中提到的 CDN 報表因為是比較新的工具,才會使用到 Terraform 。

除了沒有流水線這部分會需要建立之外,使用 CloudFormation 其實並不會有太大的問題。唯一的小問題是,一般我們會需要指定 Gitlab CI 的流水線工具的環境,但前人所留下來的環境可能因為太老舊而需要一些維護,但這並不代表無法進行相關的部署。

不過,在進行新工具的開發時,筆者仍然期待能對齊公司未來的走向,並同時也希望可以練習 Terraform 的原因,因此雖然直接複製前人留下來的 CloudFormation 部署設定是最快的,但筆者仍然嘗試著在新工具或翻新舊工具的同時,能夠使用 Terraform 來做為 IaC 的工具。當然,這一部分也是因為小工具本身是一個獨立且不會影響到其它人的個體,才能夠進行比較複雜的實驗。

想當然爾,在此真的是撞過各種雷XD

Terraform Module 與變數的傳入

在上一篇有稍微分享過筆者誤刪掉 state 這個狀態檔,而在這裡的轉換中,筆者也遇過一些其它非常有趣的問題。比如為了區分環境,筆者嘗試將各環境共用的資源做成 Terraform Module ,再在最上一層中加入可以 enable 或 disable 環境資源的另一層。最後變成總共有三層,除了 Root Module 之外,還有各環境的 Terraform Module,以及各環境共用的 Terraform Module。

但筆者在一開始並不知道,將變數從最外層傳到最內層,會需要在每一層都制定環境變數的相關設定,因此一開始一直撞到以下錯誤:

Error: Missing required argument
│ 
│   on ../env_resources/prod/module.tf line 1, in module "shared_resources":
│    1: module "shared_resources" {
│ 
│ The argument "lambda_package_s3_bucket" is required, but no definition was found.
╵
╷
│ Error: Missing required argument
│ 
│   on ../env_resources/prod/module.tf line 1, in module "shared_resources":
│    1: module "shared_resources" {
│ 
│ The argument "lambda_package_s3_key" is required, but no definition was found.

一開始筆者還以為是 module 一定要傳一個寫死的變數進去,嘗試了數次後才發現,只要有一個從最外層傳進最內層的管道,那從一開始就不會有這個問題。

打包程式的環境

另一個問題則是打包 Lambda 程式時遇到的困難。

一開始筆者所使用的是來自terraform-aws-modules/lambda/aws | Terraform Registry的 module,如下:

module "lambda_function" {
  source = "terraform-aws-modules/lambda/aws"

  function_name = "my-lambda1"
  description   = "My awesome lambda function"
  handler       = "index.lambda_handler"
  runtime       = "python3.8"

  source_path = "../src/lambda-function1"

  tags = {
    Name = "my-lambda1"
  }
}

在本機測試部署時沒有遇到任何問題,卻在 Gitlab CI 的過程中撞到以下的錯誤訊息:

Planning failed. Terraform encountered an error while generating this plan.
86╷
87│ Error: External Program Lookup Failed
88│
89│ with module.lambda_function.data.external.archive_prepare[0],
90│ on .terraform/modules/lambda_function/package.tf line 10, in data "external" "archive_prepare":
91│ 10: program = [local.python, "${path.module}/package.py", "prepare"]
92│
93│ The data source received an unexpected error while attempting to parse the
94│ query. The data source received an unexpected error while attempting to
95│ find the program.
96│
97│ The program must be accessible according to the platform where Terraform is
98│ running.
99│
100│ If the expected program should be automatically found on the platform where
101│ Terraform is running, ensure that the program is in an expected directory.
102│ On Unix-based platforms, these directories are typically searched based on
103│ the '$PATH' environment variable. On Windows-based platforms, these
104│ directories are typically searched based on the '%PATH%' environment
105│ variable.
106│
107│ If the expected program is relative to the Terraform configuration, it is
108│ recommended that the program name includes the interpolated value of
109│ 'path.module' before the program name to ensure that it is compatible with
110│ varying module usage. For example: "${path.module}/my-program"
111│
112│ The program must also be executable according to the platform where
113│ Terraform is running. On Unix-based platforms, the file on the filesystem
114│ must have the executable bit set. On Windows-based platforms, no action is
115│ typically necessary.
116│
117│ Platform: linux
118│ Program: "python3"
119│ Error: exec: "python3": executable file not found in $PATH
120╵

經過資深前輩的提點,發現是因為在該 module 在部署的過程中會進行程式的打包,而該打包會需要 Python 的套件管理工具 pip ,但因為我們所使用的部署環境沒有安裝 Python ,因此遇到上述問題。

雖然我們也有幾個包含 Python 的其它部署環境,但該環境卻沒有 Terraform ,導致筆者最後陷入動彈不得的窘境。最後,非常意外地從前人留下來的 CloudFormation 部署流水線中得到靈感,才得知可以嘗試先自己打包到 S3 後,再直接指定 Lambda 的程式檔案位置。如下:

module "lambda_function_existing_package_s3" {
  source = "terraform-aws-modules/lambda/aws"

  function_name = "my-lambda-existing-package-local"
  description   = "My awesome lambda function"
  handler       = "index.lambda_handler"
  runtime       = "python3.8"

  create_package      = false
  s3_existing_package = {
    bucket = aws_s3_bucket.builds.id
    key    = aws_s3_object.my_function.id
  }
}

部署流水線與環境控管

部署流水線在過去的專案中,主要透過 branch 的名字來決定部署的環境。比如我們會在部署規則中,制定只有在 prod、prep、qa 等名字的 branch 有新 commit 時,才會觸發部署的流水線,並在流水線規則中將 branch 的名稱代入相關的設定,比如 lambda 的名字可能是 {環境}-{專案名稱} ,而{環境}這個變數就會等於 branch 的名字。

在一開始使用 Terraform 來取代 CloudFormation 的時候,筆者仍然遵尋這一套部署原則,並透過 {環境}.tfvars 來指定環境的特定變數。比如在部署正式的時候,因為 branch 名字為prod,因此就會流水線就會自動抓到prod.tfvars這個檔案。同樣的概念也會套用在 Terraform Workspace 和任何其它與環境有關的設定上。

然而,不同的資深前輩在這個地方的設定就有了比較不一樣的看法。有人認為這會導致不同 branch 在設定上的混亂,比如在 hotfix 之後,可能會需要透過各種 cherry-pick 來同步各環境的改動。此外,有時候在前一個環境的設定,也不一定會想要套用在正式環境上,這同樣導致可能會需要 cherry-pick 或各種其它問題。

筆者其實也有吃過這個苦頭,因此經過他的建議,筆者嘗試透過 working directory 的方式來區分不同的環境,再透過 Terraform Module 的方式來共用資源。雖然這也間接導致了前一段中提到的變數導入問題,但筆者在這裡也還是學到了非常多有趣的知識。

除了應該透過 branch 還是 working directory 的方式來區分環境這個討論之外,筆者還有遇到另一個問題。在目前透過 working directory 來區分環境的情況下,環境的部署與否,應該要透過 Terraform 本身的設定,還是在流水線中加入手動部署的步驟呢?

比如是否部署 qa 環境,應該要在 Terraform 裡面提供 enable 和 disable 的設定,還是應該要在流水線中為不同的環境設定各自獨立的手動部署環節,在需要 qa 環境的時候就在部署時手動點擊該步驟呢?前者會導致每次啟用或廢止環境時都需要一個新的 commit ,而後者則可能造成不清楚部分環境不確定是否有被部署的狀況。

這個問題會在需要進行打包程式的時候變得更複雜。原本的流水線是打包完後進行部署,但在有不同環境的情況之下,每個環境都可能會需要一個獨立的「打包完進行部署」的步驟。或是說,其實可以打包的時候就所有環境都打包,之後再在不同環境透過指定打包路徑來部署?無論是哪種,都會是接下來筆者需要思考的問題。

最後,筆者非常「假𠢕(ké-gâu)」(自作聰明的台語)地認為,某些常用的流水線指令,為了避免在不同專案中重複使用的關係,因此嘗試著開啟了另一個 repository 來存放這些指令,並在每個新專案中都嘗試引用這個 repository。這本身當然沒有什麼問題但因為筆者自己學藝不精的關係,反而導致在每次修改流水線的時候,要同時修改兩個 repository。比較良好的做法,可能應該是在幾個小工具中嘗試確認沒有問題以後,再進行後續的重構才對,一開始的好高鶩遠不只沒有減輕維護上的困擾,反而增加了開發的麻煩。

後記

會在題目中提到「兼職 DevOps」,是因為流水線的建立在敝公司中其實並不是 SRE 的主要業務,而是由另一群專職的團隊來處理的。但因為小工具本身具有非常強的獨立性,因此筆者就得以在這裡自由發揮。

雖然在這個過程裡面真的是踩了非常多的雷,可以說是多到筆者有時候會開始思考到底這樣做有什麼意義,但反過來想,每次踩的雷都會成為之後的經驗,沒有想象像的那麼糟糕就是了。比如說,Gitlab 有一套為了 Terraform 專門開發的部署指令(GitLab Terraform helpers | GitLab),就是在這個過程中意外學會的。

寫到這邊,相信讀者對日常的維運都有一定程度的瞭解了。接下將會進入筆者認為十分有趣的部分,也就是重大 P0 事件簿的系列。


上一篇
日常維運4:CDN 報表自動生產 & IAM user 定期盤點
下一篇
重大P0事件簿1: 倒站又不倒站,警報過程與問題釐清
系列文
一窺SRE初心者的生活:讓警報為您的人生畫下如交響樂一般的新篇章31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言